﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Xml;

namespace ZDV.IdentityModel.Tools.Serialization
{
    public class ConfigurationHandler
    {
        private const String IdentityConfigurationXPath =
            @"/fed:configuration/fed:system.identityModel/fed:identityConfiguration";

        private readonly XmlNamespaceManager _namespaceManager;
        private readonly string _configurationFileLocation;
        private const string PrefixedSeparator = "/fed:";

        private XmlDocument Document { get; set; }

        public ConfigurationHandler(string fileLocation)
        {
            _configurationFileLocation = fileLocation;
            
            Document = new XmlDocument();
            Document.Load(_configurationFileLocation);

            Debug.Assert(Document.DocumentElement != null, "Document.DocumentElement != null");

            _namespaceManager = new XmlNamespaceManager(Document.NameTable);
            _namespaceManager.AddNamespace("fed", Document.DocumentElement.NamespaceURI);
        }

        private IEnumerable<TrustedIssuerElement> ReadTrustedIssuers(string serviceName)
        {
            var trustedIssuerNode = SelectSingleNodeFromDocument(string.Concat(new []
			{
				GetServiceXPath(serviceName),
				PrefixedSeparator,
				"issuerNameRegistry",
				PrefixedSeparator,
				"trustedIssuers"
			}));

            if (trustedIssuerNode == null)
            {
                return null;
            }

            var collection = new Collection<TrustedIssuerElement>();
            foreach (XmlNode trustedIssuerChildNode in trustedIssuerNode.ChildNodes)
            {
                Debug.Assert(trustedIssuerChildNode.Attributes != null, "trustedIssuerChildNode.Attributes != null");

                if (StringComparer.Ordinal.Equals(trustedIssuerChildNode.Name, "add"))
                {
                    var nameAttribute = (XmlAttribute)trustedIssuerChildNode.Attributes.GetNamedItem("name");
                    var thumbprintAttribute = (XmlAttribute)trustedIssuerChildNode.Attributes.GetNamedItem("thumbprint");
                    if (nameAttribute == null || thumbprintAttribute == null)
                    {
                        throw new Exception();
                    }
                    collection.Add(new TrustedIssuerElement(thumbprintAttribute.Value, nameAttribute.Value));
                }
                else
                {
                    if (StringComparer.Ordinal.Equals(trustedIssuerChildNode.Name, "remove"))
                    {
                        var xmlAttribute2 = (XmlAttribute)trustedIssuerChildNode.Attributes.GetNamedItem("thumbprint");
                        if (xmlAttribute2 == null)
                        {
                            throw new Exception();
                        }
                        for (int i = 0; i < collection.Count; i++)
                        {
                            if (StringComparer.Ordinal.Equals(collection[i].Thumbprint, xmlAttribute2.Value))
                            {
                                collection.RemoveAt(i);
                            }
                        }
                    }
                    else
                    {
                        if (StringComparer.Ordinal.Equals(trustedIssuerChildNode.Name, "clear"))
                        {
                            collection.Clear();
                        }
                    }
                }
            }
            return collection;
        }
        private void WriteTrustedIssuer(TrustedIssuerElement trustedIssuer, string serviceName)
        {
            Debug.Assert(trustedIssuer != null, "trustedIssuer != null");

            var xmlElement = CreateElementFromDocument("add");
            xmlElement.SetAttribute("thumbprint", trustedIssuer.Thumbprint);
            if (!string.IsNullOrEmpty(trustedIssuer.Name))
            {
                xmlElement.SetAttribute("name", trustedIssuer.Name);
            }

            var xmlNode = SelectSingleNodeFromDocument(string.Concat(new []
			{
				GetServiceXPath(serviceName),
				PrefixedSeparator,
				"issuerNameRegistry",
				PrefixedSeparator,
				"trustedIssuers"
			}));
            if (xmlNode == null)
            {
                xmlNode = CreateElementFromDocument("trustedIssuers");
                SelectSingleNodeFromDocument(GetServiceXPath(serviceName) + PrefixedSeparator + "issuerNameRegistry").AppendChild(xmlNode);
            }
            xmlNode.AppendChild(xmlElement);
        }

        private void ClearTrustedIssuers(String serviceName)
        {
            var xmlNode = SelectSingleNodeFromDocument(string.Concat(new[]
			{
				GetServiceXPath(serviceName),
				PrefixedSeparator,
				"issuerNameRegistry",
				PrefixedSeparator,
				"trustedIssuers"
			}));

            if(xmlNode != null)
                xmlNode.InnerXml = String.Empty;
        }

        public void UpdateIssuerNameRegistryElement(string serviceName, IEnumerable<X509Certificate2> stsCertificates, string issuerName)
        {
            var issuerNameRegistryElement = ReadIssuerNameRegistry(serviceName);
            if (issuerNameRegistryElement != null)
            {
                issuerNameRegistryElement.TrustedIssuers.Clear();
            }

            if ((issuerNameRegistryElement == null || !StringComparer.Ordinal.Equals(issuerNameRegistryElement.Type, "System.IdentityModel.Tokens.ConfigurationBasedIssuerNameRegistry, System.IdentityModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")) && !(SelectSingleNodeFromDocument("issuerNameRegistry") is XmlElement))
            {
                var xmlElement = CreateElementFromDocument("issuerNameRegistry");
                xmlElement.SetAttribute("type", "System.IdentityModel.Tokens.ConfigurationBasedIssuerNameRegistry, System.IdentityModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089");
                GetServiceNode(serviceName).AppendChild(xmlElement);
            }

            UpdateTrustedIssuers(serviceName, stsCertificates, issuerName);
        }

        private void UpdateTrustedIssuers(String serviceName, IEnumerable<X509Certificate2> stsCertificates, string issuerName)
        {
            ClearTrustedIssuers(serviceName);
            foreach (var stsCertificate in stsCertificates)
            {
                var trustedIssuer = new TrustedIssuerElement(stsCertificate.Thumbprint,
                    string.IsNullOrEmpty(issuerName)
                    ? stsCertificate.SubjectName.Name
                    : issuerName);

                WriteTrustedIssuer(trustedIssuer, serviceName);
            }
        }

        private IssuerNameRegistryElement ReadIssuerNameRegistry(string serviceName)
        {
            XmlNode xmlNode = SelectSingleNodeFromDocument(GetServiceXPath(serviceName) + PrefixedSeparator + "issuerNameRegistry");
            IssuerNameRegistryElement issuerNameRegistryElement = null;
            if (xmlNode != null)
            {
                Debug.Assert(xmlNode.Attributes != null, "xmlNode.Attributes != null");
                var xmlAttribute = (XmlAttribute)xmlNode.Attributes.GetNamedItem("type");
                if (xmlAttribute != null)
                {
                    issuerNameRegistryElement = new IssuerNameRegistryElement(xmlAttribute.Value);
                }
                if (issuerNameRegistryElement != null)
                {
                    issuerNameRegistryElement.TrustedIssuers.AddRange(ReadTrustedIssuers(serviceName));
                }
                return issuerNameRegistryElement;
            }
            return null;
        }

        private XmlElement CreateElementFromDocument(string elementName)
        {
            Debug.Assert(Document.DocumentElement != null, "Document.DocumentElement != null");
            return Document.CreateElement(elementName, Document.DocumentElement.NamespaceURI);
        }
        private XmlNode SelectSingleNodeFromDocument(string xPath)
        {
            if (string.IsNullOrEmpty(xPath))
            {
                throw new Exception();
            }
            Debug.Assert(Document.DocumentElement != null, "Document.DocumentElement != null");
            return Document.DocumentElement.SelectSingleNode(xPath, _namespaceManager);
        }

        private XmlNode GetServiceNode(string serviceName)
        {
            XmlNode xmlNode = SelectSingleNodeFromDocument(GetServiceXPath(serviceName));
            if (xmlNode == null)
            {
                XmlNode xmlNode2 = WriteXPathNodes("/fed:configuration/fed:system.identityModel");
                XmlElement xmlElement = CreateElementFromDocument("identityConfiguration");
                if (!string.IsNullOrEmpty(serviceName))
                {
                    xmlElement.SetAttribute("name", serviceName);
                }
                xmlNode2.AppendChild(xmlElement);
                return xmlElement;
            }
            return xmlNode;
        }

        private XmlNode WriteXPathNodes(string xPath)
        {
            if (string.IsNullOrEmpty(xPath))
            {
                throw new Exception();
            }
            if (!xPath.StartsWith("/", StringComparison.Ordinal))
            {
                throw new Exception();
            }
            string[] array = xPath.Split(new []
			{
				'/'
			}, StringSplitOptions.RemoveEmptyEntries);
            var stringBuilder = new StringBuilder(string.Empty);
            XmlNode xmlNode = null;
            string[] array2 = array;
            foreach (string text in array2)
            {
                string text2 = text;
                if (text.StartsWith("fed:", StringComparison.Ordinal))
                {
                    text2 = text.Replace("fed:", string.Empty);
                }
                stringBuilder.Append(PrefixedSeparator);
                stringBuilder.Append(text2);
                XmlNode xmlNode2 = SelectSingleNodeFromDocument(stringBuilder.ToString());
                if (xmlNode2 != null)
                {
                    xmlNode = xmlNode2;
                }
                else
                {
                    XmlNode xmlNode3 = CreateElementFromDocument(text2);
                    if (xmlNode != null)
                    {
                        xmlNode.AppendChild(xmlNode3);
                    }
                    xmlNode = xmlNode3;
                }
            }
            return xmlNode;
        }

        private static string GetServiceXPath(string serviceName)
        {
            string filterExpression = String.IsNullOrEmpty(serviceName)
                                          ? @"[not(@name) or @name='']"
                                          : String.Format(@"[@name and @name='{0}']", serviceName);

            var result = IdentityConfigurationXPath + filterExpression;
            return result;
        }


        public void FlushDocument()
        {
            var xmlDocument = new XmlDocument();
            xmlDocument.Load(_configurationFileLocation);
            if (!StringComparer.Ordinal.Equals(Document.OuterXml, xmlDocument.OuterXml))
            {
                var xmlTextWriter = new XmlTextWriter(_configurationFileLocation, Encoding.UTF8)
                    {Formatting = Formatting.Indented};
                Document.WriteTo(xmlTextWriter);
                xmlTextWriter.Flush();
                xmlTextWriter.Close();
            }
        }
    }
}
